package com.mybatis.lo.dao;

import com.mybatis.lo.model.ParameterMapping;
import com.mybatis.lo.po.User;
import org.apache.commons.dbcp.BasicDataSource;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;


/**
 * 面向过程实现
 * @author zhangbh
 */
public class UserDaoImplWithJdbc2 implements UserDao {


    @Override
    public User queryUserById(Integer id) {
        // 定义sql语句 ?表示占位符
        String sql = "select * from t_user where id = ?";
        List<ParameterMapping> parameterMappings = new ArrayList<>();

        parameterMappings.add(new ParameterMapping("id"));
//        User user = new User().setId(id);

        return queryOne(sql, id, User.class, "prepared", parameterMappings);
    }

    @Override
    public List<User> queryUserByName(String username) {
        // 定义sql语句
        String sql = "select * from t_user where username like '%" + username + "%'";
        return queryList(sql, null, User.class, "statement", null);
    }


    private <T> T queryOne(String sql, Object param, Class<T> resultType, String statementType, List<ParameterMapping> parameterMappings) {
        List<T> list = queryList(sql, param, resultType, statementType, parameterMappings);
        if (list == null || list.isEmpty()) {
            return null;
        } else {
            return list.get(0);
        }
    }

    private <T> List<T> queryList(String sql, Object param, Class<T> resultType, String statementType, List<ParameterMapping> parameterMappings) {
        Statement statement = null;

        Connection connection = null;
        ResultSet rs = null;

        List<T> list = new ArrayList<>();
        try {
            // 1、获取连接
            connection = getConnection();

            // 2、获取 statement
            statement = getStatement(connection, sql, statementType);

            // 3、设置参数
            setParameters(statement, param, statementType, parameterMappings);

            // 4、向数据库发出 sql 执行查询，查询出结果集
            rs = executeStatement(statement, sql, statementType);

            // 5、遍历查询结果集
            list = handleResult(rs, resultType);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6、释放资源
            releaseConnection(statement, connection, rs);
        }
        return list;
    }

    private void releaseConnection(Statement statement, Connection connection, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
            }
        }
    }

    private <T> List<T> handleResult(ResultSet rs, Class<T> resultType) throws InstantiationException, IllegalAccessException, SQLException, NoSuchFieldException {
        if (resultType == null) {
            System.out.println("传入类型为空哦 resultType");
            return null;
        }
        List<T> result = new ArrayList<>();
        while (rs.next()) {
            T data = newInstance(resultType);
            ResultSetMetaData metaData = rs.getMetaData();
            // 通过字段名+反射设置值
            int count = metaData.getColumnCount();
            for (int i = 0; i < count; i++) {
                String fieldName = metaData.getColumnName(i + 1);
                Object fieldValue = rs.getObject(fieldName);
                setFieldValue(data, fieldName, fieldValue);
            }
            // 加入返回数据
            result.add(data);
        }
        return result;
    }

    private <T> void setFieldValue(T data, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException {
        Class<?> clazz = data.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(data, fieldValue);
    }

    private <T> T newInstance(Class<T> resultType) throws InstantiationException, IllegalAccessException {
        //todo 当前按简单来
        return resultType.newInstance();
    }

    private ResultSet executeStatement(Statement statement, String sql, String statementType) throws SQLException {
        if ("prepared".equals(statementType)) {
            return ((PreparedStatement) statement).executeQuery();
        }else if ("statement".equals(statementType)){
            return statement.executeQuery(sql);
        }else if("prepareCall".equals(statementType)){
            // todo 不做处理

        }
        throw new RuntimeException("不支持执行的statementType");
    }

    private void setParameters(Statement statement, Object param, String statementType, List<ParameterMapping> parameterMappings) throws SQLException, NoSuchFieldException, IllegalAccessException {
        // 没有传递参数
        if (param == null) {
            return;
        }
        if ("prepared".equals(statementType)) {
            PreparedStatement preparedStatement = (PreparedStatement) statement;
            // map类型读取参数
            if (parameterMappings == null || parameterMappings.isEmpty()) {
                System.out.println("prepared类型没有创建对应的parameterMappings");
                return;
            }
            //判断类型
            if (isSimpleType(param)) {
                // todo 可以通过类型处理器处理set，如setInt
                preparedStatement.setObject(1, param);
            } else if (isMap(param)) {
                Map params = (Map) param;
                for (int i = 0; i < parameterMappings.size(); i++) {
                    ParameterMapping mappingName = parameterMappings.get(i);
                    preparedStatement.setObject(i + 1, params.get(mappingName));
                }
            } else if (isList(param)) {
                // todo 列表暂时不处理
            } else {
                // 普通对象类型
                for (int i = 0; i < parameterMappings.size(); i++) {
                    ParameterMapping mappingName = parameterMappings.get(i);
                    Object value = resolveValue(param, mappingName);
                    preparedStatement.setObject(i + 1, value);
                }
            }
        }

    }

    private Object resolveValue(Object o, ParameterMapping mappingName) throws NoSuchFieldException, IllegalAccessException {
        Field field = o.getClass().getDeclaredField(mappingName.getParameterName());
        field.setAccessible(true);
        return field.get(o);
    }

    private boolean isList(Object param) {
        return param instanceof List;
    }

    private boolean isMap(Object param) {
        return param instanceof Map;
    }

    private static boolean isSimpleType(Object param) {
        // 基础类型，这边就不写这么多了
        return param instanceof Integer || param instanceof String
                || param instanceof Double || param instanceof Float
                || param instanceof Boolean || param instanceof Long;
    }

    private Statement getStatement(Connection connection, String sql, String statementType) throws SQLException {
        if ("prepared".equals(statementType)) {
            return connection.prepareStatement(sql);
        } else if ("statement".equals(statementType)) {
            return connection.createStatement();
        } else if ("prepareCall".equals(statementType)) {
            return connection.prepareCall(sql);
        }
        throw new RuntimeException("不支持的 statementType=" + statementType);
    }

    private Connection getConnection() throws Exception {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/lo?characterEncoding=utf-8");
        dataSource.setUsername("root");
        dataSource.setPassword("zhangmysql@..");
        return dataSource.getConnection();
    }


}
